package com.boot.core.retry;

import com.boot.core.exception.WrappedRuntimeException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.classify.BinaryExceptionClassifier;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryState;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.retry.policy.TimeoutRetryPolicy;
import org.springframework.retry.support.DefaultRetryState;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * @author 霜寒 <1621856595@qq.com>
 * @date 2023/4/22 13:11
 **/
@Slf4j
@Service
@EnableRetry
public class RetryService {

    RetryTemplate retryTemplate;

    public RetryService() {
        TimeoutRetryPolicy retryPolicy = new TimeoutRetryPolicy();
        retryPolicy.setTimeout(TimeUnit.SECONDS.toSeconds(3));

        retryTemplate = RetryTemplate.builder()
                .retryOn(Throwable.class)
                .customPolicy(retryPolicy)
                .build();

    }

    @Retryable
    public void test1() {
        log.info("test1");
        throw new RuntimeException();
    }

    @Retryable(backoff = @Backoff(delay = 1000L, multiplier = 2))
    public void test2() throws Exception {
        log.info("test2");
        throw new RuntimeException();
    }

    @Retryable(label = "test3", value = {WrappedRuntimeException.class}, recover = "fallbackUnSafe")
    public void test3(int i) throws Exception {
        log.info("test3: {}", i);
        switch (i) {
            case 0:
                throw new Exception();
            case 1:
                throw new RuntimeException();
            case 2:
                throw new WrappedRuntimeException("Outer Wrapper", new IOException("Connection refused"));
            case 3:
                throw new Error();
            default:
                return;
        }
    }

    @Retryable(exclude = {WrappedRuntimeException.class}, recover = "fallbackSafe")
    public void test4(int i) throws Exception {
        log.info("test4: {}", i);
        switch (i) {
            case 0:
                throw new Exception();
            case 1:
                throw new RuntimeException();
            case 2:
                throw new WrappedRuntimeException("Outer Wrapper", new IOException("Connection refused"));
            case 3:
                throw new Error();
            default:
                return;
        }
    }

    @Recover
    public void fallbackUnSafe(Throwable throwable, int i) {
        log.info("fallbackUnSafe: {} throwable: {}", i, throwable.getClass());
        //throwable.printStackTrace(System.out);
    }

    @Recover
    public void fallbackSafe(Throwable throwable, int i) {
        log.info("fallbackSafe: {} throwable: {}", i, throwable.getClass());
        //throwable.printStackTrace(System.out);
    }

    public void test5(int i) {
        //当前状态的名称，当把状态放入缓存时，通过该key查询获取
        Object key = "test5";
        //是否每次都重新生成上下文还是从缓存中查询，即全局模式（如熔断器策略时从缓存中查询）
        boolean isForceRefresh = true;  //true为每次重新生成

        //对 WrappedRuntimeException 进行回滚
        BinaryExceptionClassifier rollbackClassifier =
                new BinaryExceptionClassifier(Collections.<Class<? extends Throwable>>singleton(WrappedRuntimeException.class));
        RetryState state = new DefaultRetryState(key, isForceRefresh, rollbackClassifier);

        // 通过RetryCallback 重试回调实例包装正常逻辑逻辑，第一次执行和重试执行执行的都是这段逻辑
        //RetryContext 重试操作上下文约定，统一spring-try包装
        RetryCallback<Integer, Exception> retryCallback = context -> {
            log.info("test5: {}", i);
            Thread.sleep(985);
            switch (i) {
                case 0:
                    throw new Exception();
                case 1:
                    throw new RuntimeException();
                case 2:
                    throw new WrappedRuntimeException("Outer Wrapper", new IOException("Connection refused"));
                case 3:
                    throw new Error();
                default:
                    return 16;
            }
        };
        // 通过 RecoveryCallback 重试流程正常结束或者达到重试上限后的退出恢复操作实例
        RecoveryCallback<Integer> recoveryCallback = context -> {
            log.info("重试失败, 返回默认值");
            return -1;
        };

        try {
            retryTemplate.execute(retryCallback, recoveryCallback, state);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
